// console_main.cpp : Defines the entry point for the console application.
//

#include <sstream>
#include <fstream>
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include "common/parsemsf.h"
#include "common/htmlformattedoutput.h"
#include "common/rtfformattedoutput.h"
#include "common/annotatesequence.h"
#include "common/residuegroup.h"
#include "components/perlutil.h"
using std::string;
using std::vector;
using std::cerr;

/////////////////////////////////////////////////////////////////////////////
// The one and only application object


//using namespace std;

bool ReadConfigFile(const string& file_name, CFormatVec& formats, CResidueGroupsInfo& config);
void DoSequenceAnnotations(CFormattedOutput& Engine);
void JustifyNames(CSequence& sequence, COptions::JUSTIFY_FLAG f);
void DoMainFunc(int argc, char* argv[]);
void SequenceAnnotate(CSequence& seq, CSequence& seqFormats, CResidueGroupsInfo& config,
					  const CAnnotateOptions& AnnotateOptions,
						const CParseOptions& parseOptions);
bool ReadOptionsFile(const string& file_name, CParseOptions& parseOptions, COutputOptions& outputOptions,
					 CAnnotateOptions& annotateOptions, CHTMLOptions& htmlOptions);

int main(int argc, char* argv[], char* envp[])
{
	// don't use C streams
	std::ios::sync_with_stdio(false);
	int nRetCode = 0;


	DoMainFunc(argc, argv);

	return nRetCode;
}


//88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	DoMainFunc

//88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
void Err_PassThruSequence(const string& buf, const string& errMsg)
{
	// reverse parsing procedure: replace nulls with end of line

	std::cout	<< "<HTML><BODY><PRE>\n"
				<< buf
				<< "\n</PRE></BODY></HTML>\n";
	std::cerr << errMsg << std::endl;
}

void DoMainFunc(int argc, char* argv[])
{
	vector<string> args (argv + 1, argv + argc);

	if (args.size() < 2)
	{
		std::cerr << "Usage:\n\tchroma chroma.cft chroma.opt \n";
		return;
	}

	bool use_RTF_output = false;
	if (args[0] == "--rtf")
	{
		use_RTF_output = true;
		args.erase(args.begin());
	}

	// get msf data from file
	std::ostringstream strstrm;
	if (args.size() == 3)
	{
		std::ifstream is (args[2].c_str());
		is  >> strstrm.rdbuf();
	}
	else
		std::cin >> strstrm.rdbuf();
	string buf = strstrm.str();
	// include null char
	std::vector<char> process_buf(buf.c_str(),  buf.c_str() + buf.length() + 1);

	// get residue group formats and config from file
	CFormatVec	formats;
	CResidueGroupsInfo config;
	string config_file_name = args[0];
	if (!ReadConfigFile(config_file_name, formats, config))
	{
		Err_PassThruSequence(buf, "Please specify a valid Config file");
		return;
	}

	CHTMLOptions		htmlOptions;
	CParseOptions		parseOptions;
	COutputOptions		outputOptions;
	CAnnotateOptions	annotateOptions;
	string option_file_name = args[1];
	if (!ReadOptionsFile(option_file_name, parseOptions, outputOptions, annotateOptions, htmlOptions))
	{
		Err_PassThruSequence(buf, "Please specify a valid Option file");
		return;
	}

//	Write them back out for debugging purposes
//	std::ofstream ostrm("chroma.opt");
//	ostrm << htmlOptions;
//	ostrm << annotateOptions;
//	ostrm << parseOptions;
//	ostrm << outputOptions;


	CSequence sequence;
	sequence.FormatType = GuessAlignmentFileFormat(&process_buf[0], process_buf.size());

	// parse
	CConsole_ParseMessageCallback callback;
	if (!ParseAlignmentFromBuffer(&process_buf[0], callback, sequence, parseOptions)	)
	{
		Err_PassThruSequence(buf, "This sequence was not annotated!");
		return;
	}


	// check validity ???
	sequence.ToUpper_and_Substitute(	outputOptions.m_ForceToUpper,
										outputOptions.m_SubstituteLinker,
										outputOptions.m_SubstitutingLinker,
										outputOptions.m_SubstitutedLinkers);


	// Justify names include consensus line
	JustifyNames(sequence, outputOptions.m_JustifyName);

	std::vector<CSequence> vecSeq;

	// Elide
	if (annotateOptions.m_UseElision)
		// N.B. not round(threshold) but ceiling(threshold)
		sequence.ElideSparseRegions((sequence.Count() *
									annotateOptions.m_ElisionThreshold + 999)/ 1000,
									annotateOptions.m_MinElisionLength,
									parseOptions.m_AllowLowerCase,		// get allow lower case
									parseOptions.m_InvalidResidueLetters//GetInvalidResidues
									);
//	std::cerr << "Allow lower case: " << parseOptions.m_AllowLowerCase;

	// no sub set, just use whole alignment
	vecSeq.push_back(CSequence());
	vecSeq[0].Data.swap(sequence.Data);
	vecSeq[0].Names.swap(sequence.Names);
	vecSeq[0].Len = sequence.Len;
	assert(vecSeq[0].IS_VALID());

	std::vector<CSequence> seqFormatsData;

	// create annotation
	seqFormatsData.push_back(CSequence());
	SequenceAnnotate(vecSeq[0], seqFormatsData[0], config, annotateOptions, parseOptions);

	// create consensus name
	vecSeq[0].CreateConsensusName(	outputOptions.m_OutputConsensusThreshold,
									annotateOptions.m_ConsensusThreshold);


	// justify consensus line name
	vecSeq[0].JustifyNames(outputOptions.m_JustifyName);



	// prepare to output annotation

	unsigned wrap = std::min((outputOptions.m_WrapOutput ?
		outputOptions.m_WrapOutputLen :	unsigned (-1)), sequence.Len);
	unsigned columns = std::min(outputOptions.m_ColumnSpaces ?
							outputOptions.m_ColumnWidth :
							unsigned (-1), sequence.Len);

	// create output engine
	CFormattedOutput* engine = 0;
	if (use_RTF_output)
		engine = 	new CRTFFormattedOutput(	config.TextSizeInTenths,
												config.TextFont);
	else
		engine =	new CHTMLFormattedOutput(
									config.TextSizeInTenths,
									config.TextFont,
									htmlOptions.m_HTMLStyleSheets == COptions::S_CSS, "lg",
									htmlOptions.m_HTMLSampleFont,
									htmlOptions.m_HTMLBrowserFontSize
								);

	engine->AddFormats(formats);
	engine->BeginHeader();

	// add extra data
	if (sequence.ExtraCount())
	{
		vecSeq.push_back(CSequence());
		CSequence& extraSeq = vecSeq.back();
		extraSeq.ExtraData.swap(sequence.ExtraData);
		extraSeq.ExtraNames.swap(sequence.ExtraNames);
		extraSeq.Len = sequence.Len;
		seqFormatsData.push_back(CSequence());
		seqFormatsData.back().Len = sequence.Len;
		// Justify names include consensus line
		JustifyNames(extraSeq, outputOptions.m_JustifyName);
	}


	// output annotation

	OutputAnnotatedSequence(vecSeq, seqFormatsData, wrap, columns, *engine);


	engine->EndFooter();
	std::cout << engine->GetStreamedText(0).c_str();
	delete engine;

}

//_______________________________________________________________________________________

//	ReadOptionsFile()

//_______________________________________________________________________________________
bool ReadOptionsFile(const string& option_file_name, CParseOptions& parseOptions, COutputOptions& outputOptions,
					 CAnnotateOptions& annotateOptions, CHTMLOptions& htmlOptions)
{
	std::ifstream is;
	is.open(option_file_name.c_str(), std::ios_base::in |  std::ios_base::binary);
	if (!is.is_open())
	{
		std::cerr << option_file_name << " could not be opened.\n";
		return false;
	}


	COptions::Cmap_options map_options;
	parseOptions.init_optionsmap(map_options);
	outputOptions.init_optionsmap(map_options);
	annotateOptions.init_optionsmap(map_options);
	htmlOptions.init_optionsmap(map_options);


	while (is.good())
	{
		std::string line;
		std::getline(is, line);
		std::vector<std::string> vec_string;
		PerlSplit(line, "=", vec_string);
		std::string key = vec_string[0];
		std::string value = PerlStrip(PerlJoin(vec_string, "", 1));
		COptions::Cmap_options::iterator cur = map_options.find(key);
		if (cur != map_options.end())
		{
			COptions::CLookup_SetOption& opt = cur->second;
			switch( opt.opt_type)
			{
			case COptions::eOPT_S:
				opt.opt_data.set_str(opt.opt_index, value);
				break;
			case COptions::eOPT_B:
				opt.opt_data.set_bool(opt.opt_index, value);
				break;
			case COptions::eOPT_U:
				opt.opt_data.set_uint(opt.opt_index, value);
				break;
			case COptions::eOPT_F:
				opt.opt_data.set_flag(opt.opt_index, value);
				break;
			default:
				std::cerr << "invalid opttype" << std::endl;
			}
		}
	}
	return true;
}

//_______________________________________________________________________________________

//	JustifyNames

//_______________________________________________________________________________________
void JustifyNames(CSequence& sequence, COptions::JUSTIFY_FLAG f)
{
	sequence.Names.insert(sequence.Names.begin(), CSequence::vec_str::value_type());
	sequence.Data.insert(sequence.Data.begin(), CSequence::vec_data::value_type());
	sequence.JustifyNames(f);
	sequence.Names.erase(sequence.Names.begin());
	sequence.Data.erase(sequence.Data.begin());
}





//_______________________________________________________________________________________

//	ReadConfigfile
//	[3/6/2003]

//_______________________________________________________________________________________
bool ReadConfigFile(const string& config_file_name, CFormatVec& formats, CResidueGroupsInfo& config)
{
	std::ifstream is;
	is.open(config_file_name.c_str(), std::ios_base::in |  std::ios_base::binary);
	if (!is.is_open())
	{
		std::cerr << config_file_name << " could not be opened.\n";
		return false;
	}

	if (!config.Read(is))
	{
		std::cerr	<< "There was an error reading the configuration file on line "
					<< config.GetParsingErrorLineNumber()	<< "\n"
					<< config.GetLastErrorStr() << "\n";
		return false;
	}
	config.GetResidueGrpsFormats(formats);
	return true;
}

void SequenceAnnotate(CSequence& seq, CSequence& seqFormats, CResidueGroupsInfo& config,
						const CAnnotateOptions& AnnotateOptions,
						const CParseOptions& parseOptions)
{
	// output annotation
	CStrVec	aminoAcidSets;
	string	markers;
	CFormatVec	formats;

	bool useSingleMatch = config.GetResidueGrpsFormats(formats, aminoAcidSets, markers);

	CreateAnnotation(	aminoAcidSets,
						markers,
						parseOptions.m_InvalidResidueLetters,
						AnnotateOptions,
						useSingleMatch,
						parseOptions.m_AllowLowerCase,
						seq,
						seqFormats);
}
